{
  "nbformat": 4,
  "nbformat_minor": 5,
  "metadata": {},
  "cells": [
    {
      "metadata": {},
      "source": [
        "<td>\n",
        "   <a target=\"_blank\" href=\"https://labelbox.com\" ><img src=\"https://labelbox.com/blog/content/images/2021/02/logo-v4.svg\" width=256/></a>\n",
        "</td>"
      ],
      "cell_type": "markdown"
    },
    {
      "metadata": {},
      "source": [
        "<td>\n",
        "<a href=\"https://colab.research.google.com/github/Labelbox/labelbox-python/blob/master/examples/annotation_import/dicom.ipynb\" target=\"_blank\"><img\n",
        "src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"></a>\n",
        "</td>\n",
        "\n",
        "<td>\n",
        "\n",
        "<a href=\"https://github.com/Labelbox/labelbox-python/tree/master/examples/annotation_import/dicom.ipynb\" target=\"_blank\"><img\n",
        "src=\"https://img.shields.io/badge/GitHub-100000?logo=github&logoColor=white\" alt=\"GitHub\"></a>\n",
        "</td> "
      ],
      "cell_type": "markdown"
    },
    {
      "metadata": {},
      "source": [
        "# DICOM Annotation Import\n",
        "\n",
        "* Annotations must be created and uploaded using NDJSON\n",
        "* Supported annotations that can be uploaded through the SDK:\n",
        "    * Polyline\n",
        "    * Segmentation masks     \n",
        "* **NOT** supported:\n",
        "    * Bounding box\n",
        "    * Point\n",
        "    * Polygons\n",
        "    * Free form text classifications\n",
        "    * Radio classifications \n",
        "    * Checklist classifications"
      ],
      "cell_type": "markdown"
    },
    {
      "metadata": {},
      "source": [
        "### Setup"
      ],
      "cell_type": "markdown"
    },
    {
      "metadata": {},
      "source": [
        "!pip install -q 'labelbox[data]'"
      ],
      "cell_type": "code",
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "\n",
            "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip available: \u001b[0m\u001b[31;49m22.3.1\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m23.0.1\u001b[0m\n",
            "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n"
          ]
        }
      ],
      "execution_count": null
    },
    {
      "metadata": {},
      "source": [
        "import labelbox as lb\n",
        "import labelbox.types as lb_types\n",
        "import uuid"
      ],
      "cell_type": "code",
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {},
      "source": [
        "### Replace with your API key \n",
        "Guides on [Create an API key](https://docs.labelbox.com/docs/create-an-api-key)"
      ],
      "cell_type": "markdown"
    },
    {
      "metadata": {},
      "source": [
        "# Add your api key\n",
        "API_KEY=None\n",
        "client = lb.Client(api_key=API_KEY)"
      ],
      "cell_type": "code",
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {},
      "source": [
        "## Supported annotations for DICOM\n"
      ],
      "cell_type": "markdown"
    },
    {
      "metadata": {},
      "source": [
        "######## Polyline ########\n",
        "polyline_annotation = [\n",
        "    lb_types.DICOMObjectAnnotation(\n",
        "        name=\"line_dicom\",\n",
        "        group_key=lb_types.GroupKey.AXIAL,\n",
        "        frame=1,\n",
        "        value=lb_types.Line(points=[\n",
        "            lb_types.Point(x=10, y=10),\n",
        "            lb_types.Point(x=200, y=20),\n",
        "            lb_types.Point(x=250, y=250)\n",
        "        ]),\n",
        "        segment_index=0,\n",
        "        keyframe=True,\n",
        "    ),\n",
        "    lb_types.DICOMObjectAnnotation(\n",
        "        name=\"line_dicom\",\n",
        "        group_key=lb_types.GroupKey.AXIAL,\n",
        "        frame=20,\n",
        "        value=lb_types.Line(points=[\n",
        "            lb_types.Point(x=10, y=10),\n",
        "            lb_types.Point(x=200, y=10),\n",
        "            lb_types.Point(x=300, y=300)\n",
        "        ]),\n",
        "        segment_index=1,\n",
        "        keyframe=True,\n",
        "    ),    \n",
        "]\n",
        "\n",
        "polyline_annotation_ndjson = {\n",
        "  'name': 'line_dicom',\n",
        "  'groupKey': 'axial', # should be 'axial', 'sagittal', or 'coronal'\n",
        "  'segments': [\n",
        "    {\n",
        "    'keyframes': [{\n",
        "        'frame': 1,\n",
        "        'line': [\n",
        "            {'x': 10, 'y': 10},\n",
        "            {'x': 200, 'y': 20},\n",
        "            {'x': 250, 'y': 250},\n",
        "        ]\n",
        "    }]},\n",
        "    {\n",
        "    'keyframes' : [{\n",
        "        'frame': 20,\n",
        "        'line': [\n",
        "            {'x': 10, 'y': 10},\n",
        "            {'x': 200, 'y': 10},\n",
        "            {'x': 300, 'y': 300},\n",
        "        ]\n",
        "    }]}\n",
        "    ],\n",
        "}"
      ],
      "cell_type": "code",
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {},
      "source": [
        "######## Segmentation Masks ########\n",
        "\n",
        "mask_annotation = [\n",
        "    lb_types.DICOMMaskAnnotation(\n",
        "    group_key='axial',\n",
        "    frames=[\n",
        "        lb_types.MaskFrame(\n",
        "            index=1,\n",
        "            instance_uri=\"https://storage.googleapis.com/labelbox-datasets/dicom-sample-data/sample-mask-1.png\"\n",
        "        ),\n",
        "        lb_types.MaskFrame(\n",
        "            index=5,\n",
        "            instance_uri=\"https://storage.googleapis.com/labelbox-datasets/dicom-sample-data/sample-mask-1.png\"\n",
        "        )\n",
        "    ],\n",
        "    instances=[\n",
        "        lb_types.MaskInstance(\n",
        "            color_rgb=(255, 255, 255),\n",
        "            name=\"segmentation_mask_dicom\"\n",
        "        )\n",
        "    ])\n",
        "]\n",
        "\n",
        "mask_annotation_ndjson = {\n",
        "    'groupKey': 'axial',\n",
        "    'masks': {\n",
        "        'frames': [{\n",
        "            'index': 1,\n",
        "            'instanceURI': \"https://storage.googleapis.com/labelbox-datasets/dicom-sample-data/sample-mask-1.png\"\n",
        "        }, {\n",
        "            'index': 5,\n",
        "            'instanceURI': \"https://storage.googleapis.com/labelbox-datasets/dicom-sample-data/sample-mask-1.png\"\n",
        "        }],\n",
        "        'instances': [\n",
        "            {\n",
        "                'colorRGB': (255, 255, 255),\n",
        "                'name': 'segmentation_mask_dicom'\n",
        "            }\n",
        "        ]\n",
        "    },\n",
        "}\n"
      ],
      "cell_type": "code",
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {},
      "source": [
        "## Upload Annotations - putting it all together"
      ],
      "cell_type": "markdown"
    },
    {
      "metadata": {},
      "source": [
        "### Step 1: Import data rows into Catalog"
      ],
      "cell_type": "markdown"
    },
    {
      "metadata": {},
      "source": [
        "global_key = \"sample-dicom-1.dcm\"\n",
        "asset = {\n",
        "    \"row_data\": \"https://storage.googleapis.com/labelbox-datasets/dicom-sample-data/sample-dicom-1.dcm\", \n",
        "    \"global_key\": global_key,\n",
        "}\n",
        "\n",
        "dataset = client.create_dataset(name=\"dicom_demo_dataset\")\n",
        "task = dataset.create_data_rows([asset])\n",
        "task.wait_till_done()\n",
        "print(\"Errors :\",task.errors)\n",
        "print(\"Failed data rows:\" ,task.failed_data_rows)"
      ],
      "cell_type": "code",
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Errors : None\n",
            "Failed data rows: None\n"
          ]
        }
      ],
      "execution_count": null
    },
    {
      "metadata": {},
      "source": [
        "### Step 2: Create/select an ontology\n",
        "Your project should have the correct ontology setup with all the tools and classifications supported for your annotations, and the tool names and classification instructions should match the `name` fields in your annotations to ensure the correct feature schemas are matched.\n",
        "\n",
        "For example, when we create the line annotation above, we provided the `name` as `line_dicom`. Now, when we setup our ontology, we must ensure that the name of my line tool is also `line_dicom`. The same alignment must hold true for the other tools and classifications we create in our ontology.\n",
        "\n",
        "\n",
        "[Documentation for reference ](https://docs.labelbox.com/reference/import-text-annotations)"
      ],
      "cell_type": "markdown"
    },
    {
      "metadata": {},
      "source": [
        "ontology_builder = lb.OntologyBuilder(\n",
        "    tools=[\n",
        "        lb.Tool(tool=lb.Tool.Type.RASTER_SEGMENTATION, name=\"segmentation_mask_dicom\"),\n",
        "        lb.Tool(tool=lb.Tool.Type.LINE, name=\"line_dicom\"),\n",
        "    ]\n",
        ")\n",
        "\n",
        "ontology = client.create_ontology(\"Ontology DICOM Annotations\", ontology_builder.asdict(), media_type=lb.MediaType.Dicom)"
      ],
      "cell_type": "code",
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {},
      "source": [
        "### Step 3: Create a labeling project \n",
        "Connect the ontology to the labeling project."
      ],
      "cell_type": "markdown"
    },
    {
      "metadata": {},
      "source": [
        "# Project defaults to batch mode with benchmark quality settings if this argument is not provided\n",
        "# Queue mode will be deprecated once dataset mode is deprecated\n",
        "\n",
        "project = client.create_project(name=\"dicom_project_demo\", media_type=lb.MediaType.Dicom)\n",
        "\n",
        "## connect ontology to your project\n",
        "project.setup_editor(ontology)"
      ],
      "cell_type": "code",
      "outputs": [
        {
          "name": "stderr",
          "output_type": "stream",
          "text": [
            "Default createProject behavior will soon be adjusted to prefer batch projects. Pass in `queue_mode` parameter explicitly to opt-out for the time being.\n"
          ]
        }
      ],
      "execution_count": null
    },
    {
      "metadata": {},
      "source": [
        "### Step 4: Send a batch of data rows to the project"
      ],
      "cell_type": "markdown"
    },
    {
      "metadata": {},
      "source": [
        "# Create batches\n",
        "\n",
        "# Create a batch to send to your MAL project\n",
        "batch = project.create_batch(\n",
        "  \"first-batch-dicom-demo\", # Each batch in a project must have a unique name\n",
        "  global_keys=[global_key], # a list of data row objects, data row ids or global keys\n",
        "  priority=5 # priority between 1(Highest) - 5(lowest)\n",
        ")\n",
        "\n",
        "print(\"Batch: \", batch)"
      ],
      "cell_type": "code",
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Batch:  <Batch {\n",
            "    \"consensus_settings_json\": \"{\\\"numberOfLabels\\\":1,\\\"coveragePercentage\\\":0}\",\n",
            "    \"created_at\": \"2023-03-29 15:21:44+00:00\",\n",
            "    \"name\": \"first-batch-dicom-demo\",\n",
            "    \"size\": 0,\n",
            "    \"uid\": \"6a13c820-ce45-11ed-841e-4f0ab6db95bd\",\n",
            "    \"updated_at\": \"2023-03-29 15:21:44+00:00\"\n",
            "}>\n"
          ]
        }
      ],
      "execution_count": null
    },
    {
      "metadata": {},
      "source": [
        "### Step 5: Create the annotations payload \n",
        "Create the annotations payload using the snippets of code above.\n",
        "\n",
        "Labelbox supports two formats for the annotations payload: NDJSON and Python Annotation types."
      ],
      "cell_type": "markdown"
    },
    {
      "metadata": {},
      "source": [
        "#### Python Annotation Types"
      ],
      "cell_type": "markdown"
    },
    {
      "metadata": {},
      "source": [
        "\n",
        "annotations_list = polyline_annotation + mask_annotation\n",
        "labels = [\n",
        "    lb_types.Label(\n",
        "        data=lb_types.DicomData(global_key=global_key),\n",
        "        annotations=annotations_list\n",
        "    )\n",
        "]"
      ],
      "cell_type": "code",
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {},
      "source": [
        "#### NDJSON annotations\n",
        "Here we create the complete `label_ndjson` payload of annotations. There is one annotation for each *reference to an annotation* that we created above."
      ],
      "cell_type": "markdown"
    },
    {
      "metadata": {},
      "source": [
        "label_ndjson = []\n",
        "\n",
        "for annotation in [\n",
        "    polyline_annotation_ndjson, \n",
        "    mask_annotation_ndjson\n",
        "]:      \n",
        "  annotation.update({\n",
        "      'dataRow': {\n",
        "          'globalKey': global_key\n",
        "      }\n",
        "  })\n",
        "  label_ndjson.append(annotation)\n"
      ],
      "cell_type": "code",
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {},
      "source": [
        "### Step 6: Upload annotations to a project as pre-labels or completed labels\n",
        "For the purpose of this tutorial only run one of the label imports at once, otherwise the previous import might get overwritten."
      ],
      "cell_type": "markdown"
    },
    {
      "metadata": {},
      "source": [
        "#### Model-Assisted Labeling (MAL)"
      ],
      "cell_type": "markdown"
    },
    {
      "metadata": {},
      "source": [
        "# Upload MAL label for this data row in project\n",
        "upload_job_mal = lb.MALPredictionImport.create_from_objects(\n",
        "    client = client, \n",
        "    project_id = project.uid, \n",
        "    name=\"mal_import_job-\" + str(uuid.uuid4()), \n",
        "    predictions=labels)\n",
        "\n",
        "upload_job_mal.wait_until_done();\n",
        "print(\"Errors:\", upload_job_mal.errors)\n",
        "print(\"Status of uploads: \", upload_job_mal.statuses)\n",
        "print(\"   \")"
      ],
      "cell_type": "code",
      "outputs": [
        {
          "name": "stderr",
          "output_type": "stream",
          "text": [
            "/Users/ibrahim/workspace/labelbox-python/labelbox/data/serialization/ndjson/label.py:177: UserWarning: Nested classifications are not currently supported\n",
            "                    for video object annotations\n",
            "                    and will not import alongside the object annotations.\n",
            "  warnings.warn(\n"
          ]
        },
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Errors: []\n",
            "Status of uploads:  [{'uuid': 'd193960f-1c7b-4493-87d1-8a55b2405748', 'dataRow': {'id': 'clftu3umv0384075r5poqey9o', 'globalKey': 'sample-dicom-1.dcm'}, 'status': 'SUCCESS'}, {'uuid': '8d0c454e-3385-44fc-97db-0516db3e7dc1', 'dataRow': {'id': 'clftu3umv0384075r5poqey9o', 'globalKey': 'sample-dicom-1.dcm'}, 'status': 'SUCCESS'}]\n",
            "   \n"
          ]
        }
      ],
      "execution_count": null
    },
    {
      "metadata": {},
      "source": [
        "#### Label Import"
      ],
      "cell_type": "markdown"
    },
    {
      "metadata": {},
      "source": [
        "upload_job_label_import = lb.LabelImport.create_from_objects(\n",
        "    client = client,\n",
        "    project_id = project.uid, \n",
        "    name = \"label_import_job-\" + str(uuid.uuid4()),\n",
        "    labels=labels\n",
        ")\n",
        "\n",
        "upload_job_label_import.wait_until_done()\n",
        "print(\"Errors:\", upload_job_label_import.errors)\n",
        "print(\"Status of uploads: \", upload_job_label_import.statuses)\n",
        "print(\"   \")"
      ],
      "cell_type": "code",
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Errors: []\n",
            "Status of uploads:  [{'uuid': '1cc42044-4f36-45af-8b05-da6c8dd5bf37', 'dataRow': {'id': 'clftu3umv0384075r5poqey9o', 'globalKey': 'sample-dicom-1.dcm'}, 'status': 'SUCCESS'}, {'uuid': '005e5d45-b3c2-49f5-bf82-aef760de7826', 'dataRow': {'id': 'clftu3umv0384075r5poqey9o', 'globalKey': 'sample-dicom-1.dcm'}, 'status': 'SUCCESS'}]\n",
            "   \n"
          ]
        }
      ],
      "execution_count": null
    },
    {
      "metadata": {},
      "source": [
        "### Optional deletions for cleanup"
      ],
      "cell_type": "markdown"
    },
    {
      "metadata": {},
      "source": [
        "# Delete Project\n",
        "# project.delete()\n",
        "# dataset.delete()\n"
      ],
      "cell_type": "code",
      "outputs": [],
      "execution_count": null
    }
  ]
}